查看原文
其他

OWASP 实战分析 level 1

4Chan 看雪学苑 2023-12-12
这篇文章详细介绍了解决OWASP人员(Bernhard Mueller)发布的Androidcrackme程序(owasp uncrackable)的几种方法。


题目链接


链接:https://pan.baidu.com/s/1VJ7Y3psWoSi5NnlOpaohEw
提取码:1234


owasp uncrackable 安全机制

owasp uncrackable能够找到的安全机制:


1.Java反调试

2.Java完整性校验(CRC)

3.java Root检测

4.Native层的反调试



使用到的工具


Java层的反编译工具(Dalvik bytecode):

◆Jadx-gui.

◆JEB.


So层反编译程序:

◆IDA Pro


动态二进制检测框架:

◆Frida.


编译工具

◆vscode


owasp level1


使用Jadx反编译工具打开apk如下所示。


来到MainActivity入口:



猜测会有root权限的检测工具,顺着这个猜测,我们可以有两种思路解决这个root检测。

1.动态调试smail代码,修改代码绕过root检测

2.使用fridahook绕过


继续往下看:


public void verify(View view) {
String str;
//获取输入框的字符串
String obj = ((EditText) findViewById(R.id.edit_text)).getText().toString();
AlertDialog create = new AlertDialog.Builder(this).create();
//我们需要进入的地方
if (a.a(obj)) {
create.setTitle("Success!");
str = "This is the correct secret.";
} else {
create.setTitle("Nope...");
str = "That's not it. Try again.";
}
create.setMessage(str);
// 设置对话框的按钮
create.setButton(-3, "OK", new DialogInterface.OnClickListener() { // from class: sg.vantagepoint.uncrackable1.MainActivity.2
@Override // android.content.DialogInterface.OnClickListener
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.dismiss();
}
});
// 显示对话框
create.show();
}


我们进入a类:



进入后,内容如下:


public class a {
// 判断字符串是否与加密后的字符串相等
public static boolean a(String str) {
byte[] bArr;
byte[] bArr2 = new byte[0];
try {
// 对字符串进行AES解密
bArr = sg.vantagepoint.a.a.a(b("8d127684cbc37c17616d806cf50473cc"), Base64.decode("5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc=", 0));
} catch (Exception e) {
// 打印AES解密错误信息
Log.d("CodeCheck", "AES error:" + e.getMessage());
bArr = bArr2;
}
// 判断解密后的字符串与给定字符串是否相等
return str.equals(new String(bArr));
}
// 将字符串转换为字节数组
public static byte[] b(String str) {
int length = str.length();
byte[] bArr = new byte[length / 2];
for (int i = 0; i < length; i += 2) {
// 将字符串中的每两个字符转换为对应的字节
bArr[i / 2] = (byte) ((Character.digit(str.charAt(i), 16) << 4) + Character.digit(str.charAt(i + 1), 16));
}
return bArr;
}
}


经过观察,发现如果成功返回equals(true),就能suceeful,由于没有涉及到so层(没看到native),所以我们直接在Java层进行代码分析。


1.Base64解密字节数组



手写一下伪代码:


java

public static byte[] getdecode(String word){
byte[] base64decodedBytes = Base64.getDecoder().decode(word);
byte[] newbase64decodedBytes =Base64.getDecoder().decode(word);
String strcode = new String(newbase64decodedBytes);
System.out.println(strcode);
System.out.println(newbase64decodedBytes);

return base64decodedBytes;
}


js实现上述java函数。


getBase64decode = function (){
var encode = "5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc=";
let decrypted = atob(encode);
//转成字节数组
let base64decodedBytes = new Uint8Array(atob(encode).split('').map((char) => char.charCodeAt(0)));
return base64decodedBytes;

}


2.对字符串"8d127684cbc37c17616d806cf50473cc" 使用sg.vantagepoint.a.a.a进行处理



java


public static byte[] b(String str) {
int length = str.length();
byte[] bArr = new byte[length / 2];
for (int i = 0; i < length; i += 2) {
bArr[i / 2] = (byte) ((Character.digit(str.charAt(i), 16) << 4) + Character.digit(str.charAt(i + 1), 16));
}
return bArr;
}


使用js完成java的功能。


const byte = (x) => ((((x | 0) & 0xff) + 128) % 256) - 128;
//判断是否大小写
Judgment_capitalization = function (input){
if ( 'A'<=input &&input <='Z'){
return true;
}
else
return false;
}
//判断是否小写
Determine_whether_to_lower = function (input){
if ( 'a'<=input &&input <='z'){
return true;
}
else
return false;
}
digit = function (input,radis) {

try {
if (!isNaN(parseInt(input))) {
if (parseInt(input) < 10 && parseInt(input) > -1) {
return parseInt(input) < parseInt(radis) ? parseInt(input) : -1;
}
}
} catch (err) {
}
try {
if (Judgment_capitalization(input)) {
return input.charCodeAt(0) < radis + 'A'.charCodeAt(0) - 10 ? input.charCodeAt(0) - 'A'.charCodeAt(0) + 10 : -1;
}
} catch (err) {

}
try {
if (Determine_whether_to_lower(input)) {
return input.charCodeAt(0) < radis + 'a'.charCodeAt(0) - 10 ? input.charCodeAt(0) - 'a'.charCodeAt(0) + 10 : -1;
}
} catch (err) {
}

}
b = function (paramString){
var nlength = paramString.length;
//直接使用intarrybuffer
var arrofByte = new Int8Array(nlength/2);
for(var i=0;i<nlength;i+=2){
arrofByte[i/2] = (byte)(((digit(paramString.charAt(i),16)<<4)+digit(paramString.charAt(i+1),16)));
}
return (arrofByte);
}


3.查看外层函数直接sg.vantagepoint.a,点击进入



类如下:


public class a {
public static byte[] a(byte[] bArr, byte[] bArr2) {
SecretKeySpec secretKeySpec = new SecretKeySpec(bArr, "AES/ECB/PKCS7Padding");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(2, secretKeySpec);
return cipher.doFinal(bArr2);
}
}


这个时候,我们综合之前的分析,可以写出java脚本。


import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.Security;
import java.util.Base64;

public class AES128ECBwithPKCS7 {

private static final String SECRET = "AES";
private static final String CIPHER_ALGORITHM = "AES/ECB/PKCS7Padding";

// Decrypts the given byte array using AES 128 ECB with PKCS7 padding
public static String newaes256ECBPkcs7PaddingDecrypt(byte[] bArr, byte[] bArr2) throws Exception {
String strbrr = new String(bArr);
String strbrr2 = new String(bArr2);

// Create a new instance of the Cipher class with the specified algorithm
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);

// Initialize the cipher in decryption mode with the secret key
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(bArr, SECRET));

// Perform the decryption operation
byte[] doFinal = cipher.doFinal(bArr2);

// Convert the decrypted byte array to a string and return it
return new String(doFinal);
}

// Converts a hexadecimal string to a byte array
public static byte[] b(String str) {
int length = str.length();
byte[] bArr = new byte[length / 2];

// Iterate over the hexadecimal string and convert each pair of characters to a byte
for (int i = 0; i < length; i += 2) {
bArr[i / 2] = (byte) ((Character.digit(str.charAt(i), 16) << 4) + Character.digit(str.charAt(i + 1), 16));
}

// Print the byte array
System.out.println(bArr);

return bArr;
}

// Decodes a Base64-encoded string and returns the decoded byte array
public static byte[] getdecode(String word) {
byte[] base64decodedBytes = Base64.getDecoder().decode(word);
byte[] newbase64decodedBytes = Base64.getDecoder().decode(word);
String strcode = new String(newbase64decodedBytes);

// Print the decoded string and byte array
System.out.println(strcode);
System.out.println(newbase64decodedBytes);

return base64decodedBytes;
}

public static void main(String[] args) throws Exception {
// Decrypt the given text using AES 128 ECB with PKCS7 padding
String text = newaes256ECBPkcs7PaddingDecrypt(b("8d127684cbc37c17616d806cf50473cc"), getdecode("5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc="));

// Print the decrypted text
System.out.println(text);
}
}


js脚本


const CryptoJS = require('crypto-js')

//转化为byte
const base = require("./Crypted/Base64");
const byte = (x) => ((((x | 0) & 0xff) + 128) % 256) - 128;

//判断是否大小写
Judgment_capitalization = function (input){
if ( 'A'<=input &&input <='Z'){
return true;
}
else
return false;
}

//判断是否小写
Determine_whether_to_lower = function (input){
if ( 'a'<=input &&input <='z'){
return true;
}
else
return false;

}

digit = function (input,radis) {

try {
if (!isNaN(parseInt(input))) {
if (parseInt(input) < 10 && parseInt(input) > -1) {
return parseInt(input) < parseInt(radis) ? parseInt(input) : -1;
}
}

} catch (err) {
}
try {
if (Judgment_capitalization(input)) {
return input.charCodeAt(0) < radis + 'A'.charCodeAt(0) - 10 ? input.charCodeAt(0) - 'A'.charCodeAt(0) + 10 : -1;
}
} catch (err) {

}
try {
if (Determine_whether_to_lower(input)) {
return input.charCodeAt(0) < radis + 'a'.charCodeAt(0) - 10 ? input.charCodeAt(0) - 'a'.charCodeAt(0) + 10 : -1;
}
} catch (err) {
}

}
b = function (paramString){
var nlength = paramString.length;
//直接使用intarrybuffer
var arrofByte = new Int8Array(nlength/2);
for(var i=0;i<nlength;i+=2){
arrofByte[i/2] = (byte)(((digit(paramString.charAt(i),16)<<4)+digit(paramString.charAt(i+1),16)));
}
return (arrofByte);
}

/**
*@description:将string转为UTF-8格式signed char字节数组
*
*/
function byteToString(arr) {
if(typeof arr === 'string') {
return arr;
}
var str = '',
_arr = arr;
for(var i = 0; i < _arr.length; i++) {
var one = _arr[i].toString(2),
v = one.match(/^1+?(?=0)/);
if(v && one.length == 8) {
var bytesLength = v[0].length;
var store = _arr[i].toString(2).slice(7 - bytesLength);
for(var st = 1; st < bytesLength; st++) {
store += _arr[st + i].toString(2).slice(2);
}
str += String.fromCharCode(parseInt(store, 2));
i += bytesLength - 1;
} else {
str += String.fromCharCode(_arr[i]);
}
}
return str;
}


//网络传输
function stringToByte(str) {
var len, c;
len = str.length;
var bytes = [];
for(var i = 0; i < len; i++) {
c = str.charCodeAt(i);
if(c >= 0x010000 && c <= 0x10FFFF) {
bytes.push(((c >> 18) & 0x07) | 0xF0);
bytes.push(((c >> 12) & 0x3F) | 0x80);
bytes.push(((c >> 6) & 0x3F) | 0x80);
bytes.push((c & 0x3F) | 0x80);
} else if(c >= 0x000800 && c <= 0x00FFFF) {
bytes.push(((c >> 12) & 0x0F) | 0xE0);
bytes.push(((c >> 6) & 0x3F) | 0x80);
bytes.push((c & 0x3F) | 0x80);
} else if(c >= 0x000080 && c <= 0x0007FF) {
bytes.push(((c >> 6) & 0x1F) | 0xC0);
bytes.push((c & 0x3F) | 0x80);
} else {
bytes.push(c & 0xFF);
}
}
return new Int8Array(bytes);
}
getBase64decode = function (){


var encode = "5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc=";
let decrypted = atob(encode);
let base64decodedBytes = new Int8Array(atob(encode).split('').map((char) => char.charCodeAt(0)));
// var bytedecode = new Int8Array(100)
// bytedecode =stringToByte(decrypted);
const strbrr = String.fromCharCode.apply(null,base64decodedBytes);

console.log(strbrr);
return base64decodedBytes;

}


//AES_ECB模式解密
deCryptedByAes_ECB = function (data,AESkey){
var strdata = byteToString(data);
var strAESKey = byteToString(AESkey)
var key = CryptoJS.enc.Utf8.parse(AESkey);
var decrypt = CryptoJS.AES.decrypt(data, key, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7,
});
return CryptoJS.enc.Utf8.stringify(decrypt).toString();
把加密的内容和秘钥转化为wordarry对象
// const encryptedwordarry = CryptoJS.enc.Hex.parse(data.join(''));
// const keywordArry = CryptoJS.enc.Utf8.parse(AESkey.join(''));
// //解密
// const decrypted = CryptoJS.AES.decrypt(
// {ciphertext:encryptedwordarry},
// keywordArry,{mode:CryptoJS.mode.ECB,padding:CryptoJS.pad.Pkcs7}
// )
// return decrypted.toString(CryptoJS.enc.Utf8);

}

const str = byteToString(b("8d127684cbc37c17616d806cf50473cc"))

gettext = function () {
var text = deCryptedByAes_ECB(b("8d127684cbc37c17616d806cf50473cc"),getBase64decode("5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc="))
console.log(text);
}

console.log(gettext())


跑出的结果是:



就是我们想要的flag。


注意,此时并没有绕过root检测,就已经完成了分析过程,但为了尊重开发者,我们按照之前的两个思路进行解题。


1.1. 使用AS动态修改smail代码进行绕过


1.使用Apk killer进行反编译



smali文件丢到其他目录下,方便我们接下来使用AS导入。


丢到H盘,把smail修改该名字为src


AS(Android Studio下面统称AS)导入smalidea-0.06.zip。



导入后会提示重启AS。


重启AS,然后开始进行下面修改。


file->setting->editor->filetype `选择 `smail Files`,然后点击右上方的+号,输入`*.smail


如果没有找到smail Files说明在第三步(AS(Android Studio下面统称AS)导入smalidea-0.06.zip)出错,错误原因可能是当前AS的版本太低(低于4.0)应该使用smalidea-0.05.zip或者是当前AS版本(高于4.0)较高,但导入了低版本的smalidea-0.05.zip。


新建项目,导入文件smail文件。



Oncreate函数下断点。



1.输入命令开启调试adb shell am start -D -n owasp.mstg.uncrackable1/sg.vantagepoint.uncrackable1.MainActivity


这是一条用于在Android设备上启动应用程序的ADB命令。具体含义如下:
● `adb shell`:通过adb连接到设备的shell终端。
● `am start`:启动ActivityManager服务,用于启动应用程序。 ● -D:在启动应用程序时,将应用程序置于调试模式。
● `-n wasp.mstg.uncrackable1/sg.vantagepoint.uncrackable1.MainActivity`:指定要启动的应用程序的包名和主Activity的类名。 总体来说,这条命令的作用是以调试模式启动包名为`owasp.mstg.uncrackable1`的应用程序,并打开其`MainActivity`。


运行如下

<img src="https://bbs.kanxue.com/upload/attach/202310/940967_RFDNEHHANPPKYHD.png" alt="image-20231024110212864" style="zoom:50%;" />


1.找到包名对应的pidadb shell ps | grep owasp.mstg.uncrackable1


这里是8980使用adb命令 转发本地端口。


adb forward tcp:8800 jdwp:8980


设置远程调试


点击右上方AddConfiguration ...



点击Remote JVM Debug



设置端口8800和名字app1。


选择设备,并开始调试。



由于root检测,远程调试无法进入OnCreated界面,所以选择直接看smaile源码。



思路是:直接在smail层绕过Rootdebug检测。


来到smail代码层需要修改绕过(Root函数代码)代码如下:



修改完后使用killer重新编译一下。



安装apk到手机,显示已经没有root检测窗口了。



这个时候可以开启动态调试了,如图:



按照这个思路,我们也可以直接跳转到登录成功的界面,修改的smail代码如下:



重新编译,无论输入什么都能成功登录。


1.2. 使用Frida进行hook java层


没什么好说的,直接hook一把梭。


setImmediate(function (){ // 在下一个事件循环中执行以下代码

Java.perform(function() {
// 重写MainActivity类中的a方法
Java.use("sg.vantagepoint.uncrackable1.MainActivity").a.implementation = function (x) {
// 注释:这里是a方法的重写实现
// this.a(x);
}

// 获取sg.vantagepoint.a.a类的实例
var a = Java.use("sg.vantagepoint.a.a");

// 重写a类中的a方法
a["a"].implementation = function (bArr, bArr2) {
// 注释:这里是a方法的重写实现
console.log('a is called' + ', ' + 'bArr: ' + bArr + ', ' + 'bArr2: ' + bArr2);

// 调用原始的a方法并获取返回值
var ret = this.a(bArr, bArr2);

// 打印返回值
console.log('a ret value is ' + ret);

// 将返回值转换为字符串并打印
console.log(Java.use("java.lang.String").$new(ret));

// 返回原始的返回值
return ret;
};
})
})






看雪ID:4Chan

https://bbs.kanxue.com/user-home-940967.htm

*本文为看雪论坛优秀文章,由 4Chan 原创,转载请注明来自看雪社区


# 往期推荐

1、【远控木马】银狐组织最新木马样本-分析

2、关于迷宫题的一些求解思路

3、Android第一代落地DEX加固实战学习

4、手游完整性校验分析

5、魔法打败魔法:Frida过Frida检测

6、Typora保护机制与注册逆向分析




球分享

球点赞

球在看

继续滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存